package com.example.exceldemo.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.enums.CellExtraTypeEnum;

import com.alibaba.excel.read.metadata.ReadSheet;
import com.example.exceldemo.constant.QuotationConstant;
import com.example.exceldemo.entity.*;
import com.example.exceldemo.enuns.SheetType;
import com.example.exceldemo.exception.ServiceException;
import com.example.exceldemo.listener.DynamicEasyExcelListener;
import com.example.exceldemo.listener.EasyExcelListener;
import com.example.exceldemo.resp.CrossResp;
import com.example.exceldemo.resp.DynamicCellResp;
import com.example.exceldemo.resp.ProductResp;
import com.example.exceldemo.service.easyIpmExcelService;
import com.example.exceldemo.utils.easy.ExcelUtils;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Consumer;

import static com.example.exceldemo.enuns.SheetType.getByDesc;

/**
 * @Description
 * @Author xiaohuang
 * @Date 2025/2/21
 */
@Service
public class easyIpmExcelServiceImpl implements easyIpmExcelService {

    private static final Logger log = LoggerFactory.getLogger(easyIpmExcelServiceImpl.class);


    @Override
    public DynamicCellResp dynamicCell(MultipartFile file) {
        DynamicCellResp resp = new DynamicCellResp();
        try {
            // 1. 将文件内容缓存到字节数组（解决流重复使用问题）
            byte[] fileBytes = file.getBytes();
            // 2. 预读取Sheet信息（使用独立的流）
            List<String> sheetNames = getSheetNames(fileBytes);
            // 3. 使用新的流处理实际数据读取
            processExcelSheets(fileBytes, sheetNames, resp);
        } catch (IOException e) {
            log.error("文件处理失败: {}", e.getMessage(), e);
            throw new RuntimeException("文件处理失败", e);
        }
        return resp;
    }


    /**
     * 处理Excel文件中的所有Sheet数据
     *
     * @param fileBytes  文件内容的字节数组
     * @param sheetNames Sheet名称列表
     */
    private void processExcelSheets(byte[] fileBytes, List<String> sheetNames, DynamicCellResp resp) {
        try (InputStream stream = new ByteArrayInputStream(fileBytes);
             ExcelReader excelReader = EasyExcel.read(stream).extraRead(CellExtraTypeEnum.MERGE).build()) {
            for (int sheetIndex = 0; sheetIndex < sheetNames.size(); sheetIndex++) {
                String sheetName = sheetNames.get(sheetIndex);
                if (sheetName.isEmpty()) {
                    log.warn("跳过无名Sheet: {}", sheetIndex);
                    continue;
                }
                createReadSheet(sheetName, sheetIndex, excelReader, resp);
            }
        } catch (IOException e) {
            throw new RuntimeException("Excel读取失败", e);
        }
    }

    /**
     * 获取Excel文件中所有Sheet的名称
     *
     * @param fileBytes 文件内容的字节数组
     * @return Sheet名称列表
     * @throws IOException 如果读取文件失败
     */
    private List<String> getSheetNames(byte[] fileBytes) throws IOException {
        List<String> sheetNames = new ArrayList<>();
        try (InputStream stream = new ByteArrayInputStream(fileBytes);
             Workbook workbook = WorkbookFactory.create(stream)) {
            int sheetCount = workbook.getNumberOfSheets();
            for (int i = 0; i < sheetCount; i++) {
                String name = workbook.getSheetName(i);
                sheetNames.add(name != null ? name.trim() : "");
            }
        } catch (InvalidFormatException e) {
            throw new IOException("无效的文件格式", e);
        }
        return sheetNames;
    }

    /**
     * 根据sheet名称和索引创建并处理Excel读取sheet
     * 此方法根据sheet的类型执行不同的处理逻辑，跳过未配置或未识别的sheet类型
     *
     * @param sheetName   Excel工作表的名称，用于日志和识别sheet类型
     * @param sheetIndex  Excel工作表的索引，用于指定要读取的sheet
     * @param excelReader Excel读取器实例，用于执行读取操作
     * @param resp        用于存储读取结果的响应对象
     */
    private void createReadSheet(String sheetName, int sheetIndex, ExcelReader excelReader, DynamicCellResp resp) {
        // 根据sheet名称获取sheet类型描述
        SheetType desc = getByDesc(sheetName);
        // 如果sheet类型未配置，则记录日志并返回
        if (desc == null) {
            log.info("跳过未配置的Sheet: {}", sheetName);
            return;
        }
        try {
            // 根据不同的sheet类型执行相应的处理逻辑
            switch (desc) {
                case VERTICALLY_MERGE_CELLS:
                    // 处理垂直合并单元格的sheet
                    processSheet(sheetIndex, excelReader, CrossResp.class, 1, listener -> resp.setVerticalMergeCell(listener.getList()));
                    break;
                case HEAD_MERGES_CELLS_VERTICALLY:
                    // 处理标题垂直合并单元格的sheet
                    processSheet(sheetIndex, excelReader, CrossResp.class, 2, listener -> resp.setVerticalMergeCellAndHead(listener.getList()));
                    break;
                case PORTRAIT_ROW_MERGE_CELLS:
                    // 处理纵向合并单元格的sheet
                    processSheet(sheetIndex, excelReader, BaseData.class, 1, listener -> resp.setHorizontalAndVerticalMergeCell(listener.getList()));
                    break;
                case SIMPLE_DERIVATION:
                    // 处理简单导出的sheet
                    processSheet(sheetIndex, excelReader, BaseData.class, 1, listener -> resp.setSimpleExport(listener.getList()));
                    break;
                default:
                    // 如果sheet类型未识别，则记录日志
                    log.info("跳过未识别的Sheet类型: {}", sheetName);
            }
        } catch (Exception e) {
            log.error("处理Sheet时发生异常: {}", sheetName, e);
        }
    }

    /**
     * 处理指定的Excel工作表
     * 此方法负责根据提供的参数读取Excel工作表，并将读取的结果通过回调函数传递给调用者
     *
     * @param sheetIndex    Excel工作表的索引，用于指定要读取的sheet
     * @param excelReader   Excel读取器实例，用于执行读取操作
     * @param clazz         表示sheet数据模型的类类型
     * @param headRowNumber 表头行数，用于指定数据开始的行号
     * @param callback      用于处理读取结果的回调函数，读取完成后调用
     * @param <T>           泛型参数，表示sheet数据模型的类型
     */
    private <T> void processSheet(int sheetIndex, ExcelReader excelReader, Class<T> clazz, int headRowNumber, Consumer<DynamicEasyExcelListener<T>> callback) {
        // 创建动态Excel读取监听器
        DynamicEasyExcelListener<T> listener = new DynamicEasyExcelListener<>(null, headRowNumber);
        // 构建读取sheet配置
        ReadSheet readSheet = EasyExcel.readSheet(sheetIndex)
                .headRowNumber(headRowNumber)
                .head(clazz)
                .registerReadListener(listener)
                .build();
        excelReader.read(readSheet);
        // 通过回调函数处理读取结果
        callback.accept(listener);
    }

    @Override
    public ProductResp saveExcelInfo(MultipartFile file) {
        ProductResp resp = new ProductResp();
        try (InputStream stream = file.getInputStream()) {
            EasyExcelListener easyExcelListener = new EasyExcelListener();
            // 读取excel内容
            EasyExcelFactory.read(stream, easyExcelListener).sheet(0).doRead();
            // 获取表格内容
            List<LinkedHashMap> listMap = easyExcelListener.getListMap();
            settlementMonthlyReport(listMap, resp);
            // 处理基本信息和表格
        } catch (IOException | NullPointerException e) {
            throw new ServiceException("导入数据时发生错误: " + e.getMessage(), e);
        }
        return resp;
    }

    private void settlementMonthlyReport(List<LinkedHashMap> maps, ProductResp productResp) {
        // 基本资料
        ElectricalProduct baseInfo = new ElectricalProduct();
        // 报价表明细
        List<LinkedHashMap> quotationDetails = new ArrayList<>();
        // 是否基本资料
        boolean isBaseInfo = true;
        // 是否报价表
        boolean isQuotation = false;

        for (LinkedHashMap map : maps) {
            if (isBaseInfo) {
                // 基本资料
                isBaseInfo = dealBaseInfo(map, baseInfo);
                if (!isBaseInfo)
                    continue;
            }
            // “报价表”-表格标题：合并单元格，数值在左上角
            Object obj = map.get("0");
            String str = null;
            if (ObjectUtil.isNotEmpty(obj))
                str = obj.toString().replaceAll("\\p{Z}", "");
            // 判断是否是“报价表”
            if (StringUtils.isNotBlank(str) && str.contains(QuotationConstant.QUOTATION_TABLE_TITLE))
                isQuotation = true;

            if (!isBaseInfo && isQuotation)
                // 报价表-表格
                isQuotation = dealQuotationDetails(map, quotationDetails, str);

        }

        if (!quotationDetails.isEmpty()) {
            // 将从Excel中读取的数据转换为指定类型的对象列表
            List<Quotation> dtos = ExcelUtils.convertList(quotationDetails, Quotation.class);
            productResp.setQuotations(dtos);
        }
        productResp.setProduct(baseInfo);
    }

    private boolean dealBaseInfo(LinkedHashMap map, ElectricalProduct baseInfo) {
        // 是否基础信息内容
        //基本信息
        if (map.containsValue(QuotationConstant.BASE_INFO_TITLE))
            return true;

        // 根据表格内容分析，对象值在2,9列。合并单元格在读取之后会将值存在合并的第一列索引下，如合并A-C列，那A列会存储字段值，B、C列值为空。
        Object obj = map.get("2");
        String str = ObjectUtil.isNotEmpty(obj) ? obj.toString() : null;
        Object object = map.get("9");
        String value = ObjectUtil.isNotEmpty(object) ? object.toString() : null;

        // 通过本行的第一个字段名，获取本行的所有字段信息(后面的判断均是如此，常量类对应了各个字段名)
        //询价单位
        if (map.containsValue(QuotationConstant.QUOTE_UNIT)) {
            baseInfo.setUnit(str);
            baseInfo.setContact(value);
        }
        //项目名称
        if (map.containsValue(QuotationConstant.PROJECT_NAME)) {
            baseInfo.setProjectName(str);
            baseInfo.setContactWay(value);
        }
        //项目地址
        if (map.containsValue(QuotationConstant.PROJECT_ADDRESS)) {
            baseInfo.setProjectAddress(str);
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
            baseInfo.setQuoteTime(LocalDate.parse(value, formatter));
        }
        //报价单位
        if (map.containsValue(QuotationConstant.QUOTE_UNIT)) {
            baseInfo.setQuoteUnit(str);
            baseInfo.setQuotePerson(value);
            // 基本信息赋值完
            return false;
        }
        return true;
    }

    private Boolean dealQuotationDetails(LinkedHashMap map, List<LinkedHashMap> quotationDetails, String firstStr) {
        // 确定模块结束标识（“合   计（大写）”）
        if (StringUtils.isNotBlank(firstStr) && firstStr.contains(QuotationConstant.TOTAL_ROW_TITLE))
            return false;

        // 模块开始标识为模块标题("报价表")以及模块第一行字段名（“序号”），所以读取数据时要排除这两行
        if (StringUtils.isNotBlank(firstStr) && !map.containsValue(QuotationConstant.SEQUENCE_NUMBER) && !firstStr.contains(QuotationConstant.QUOTATION_TABLE_TITLE)) {
            quotationDetails.add(map);
        }
        return true;
    }


}
